/*
 * Copyright ©2015-2021 Jaemon. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.jaemon.spring.adorner.handler;

import io.gitee.jaemon.spring.adorner.AdornerConstants;
import io.gitee.jaemon.spring.adorner.annotation.FieldNote;
import io.gitee.jaemon.spring.adorner.core.AbstractSpringParamHandler;
import io.gitee.jaemon.spring.adorner.core.AdornerProperty;
import io.gitee.jaemon.spring.adorner.core.DocContainer;
import io.gitee.jaemon.spring.adorner.exception.AdornerException;
import io.gitee.jaemon.spring.adorner.type.ParamPair;
import io.gitee.jaemon.spring.adorner.util.AdornerUtils;
import io.gitee.jaemon.spring.adorner.util.AnnotationUtils;
import io.gitee.jaemon.spring.adorner.util.ClassUtils;
import io.gitee.jaemon.spring.adorner.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.time.LocalDate;
import java.util.*;

import static io.gitee.jaemon.spring.adorner.AdornerConstants.*;
import static io.gitee.jaemon.spring.adorner.util.AdornerUtils.paramTable;
import static io.gitee.jaemon.spring.adorner.util.MarkdownUtils.JS_SINGLE_LINE_COMMENTS;
import static io.gitee.jaemon.spring.adorner.util.MarkdownUtils.LINE_BREAK;

/**
 * JSON参数处理器
 *
 * @author Jaemon
 * @since 1.0
 */
public class JsonParamHandler extends AbstractSpringParamHandler<ParamPair> {
    public static final String JSON_LEFT_SYMBOL = "{";
    public static final String JSON_RIGHT_SYMBOL = "}";

    public static final Map<String, String> defaultValue = new HashMap<>();
    static {
        defaultValue.put("byte", "0");
        defaultValue.put("short", "0");
        defaultValue.put("int", "0");
        defaultValue.put("long", "0");
        defaultValue.put("float", "0.0");
        defaultValue.put("double", "0.0");
        defaultValue.put("char", "");
        defaultValue.put("boolean", "false");
    }

    @Override
    public ParamPair requestHandler(Method method) throws AdornerException {
        return requestJson(method);
    }

    @Override
    public ParamPair responseHandler(Method method) throws AdornerException {
        return responseJson(method);
    }

    @Override
    public boolean notes() {
        return Boolean.parseBoolean(
                AdornerProperty.INSTANCE.get(SPRING_ADORNER_JSON_HANDLER_NOTES)
        );
    }

    protected ParamPair requestJson(Method method) {
        StringBuilder resultJson = new StringBuilder();
        StringBuilder resultTable = new StringBuilder();
        // 方法参数注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 获取方法参数的泛型参数类型
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        Parameter[] parameters = method.getParameters();

        List<ParamMap> validParameters = new ArrayList<>();
        for (int i = 0; i < parameterTypes.length; i++) {
            boolean exists = AnnotationUtils.containAnnotation(parameterAnnotations[i], requestAnnotation());
            if (!exists) {
                continue;
            }
            validParameters.add(new ParamMap(parameterTypes[i], i));
        }
        boolean onlyOne = validParameters.size() == 1;

        if (validParameters.isEmpty()) {
            return ParamPair.of(resultJson.toString());
        }

        String[] paramJsons = new String[validParameters.size()];
        String paramJson;
        for (int i = 0, size = validParameters.size(); i < size; i++) {
            ParamMap paramMap = validParameters.get(i);
            // 方法参数类型
            Class<?> parameterType = parameterTypes[paramMap.index];
            TypeVariable<? extends Class<?>>[] typeParameters = parameterType.getTypeParameters();

            paramJson = fieldsJson(parameterType, onlyOne ? 1 : 2);
            if (typeParameters.length == 0) {
                resultTable.append(paramTable(parameterType)).append(NEWLINE);
            }

            Type genericParameterType = genericParameterTypes[paramMap.index];
            // 对方法参数泛型参数进行处理
            StringBuilder multiTable = new StringBuilder();
            if (ParameterizedType.class.isInstance(genericParameterType)) {
                ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

                if (typeParameters.length != actualTypeArguments.length) {
                    continue;
                }

                for (int m = 0; m < actualTypeArguments.length; m++) {
                    Type actualTypeArgument = actualTypeArguments[m];
                    String genericJson = genericTypeValue(actualTypeArgument, onlyOne ? 2 : 3);
                    paramJson = paramJson.replaceAll(
                            genericPlaceholder(typeParameters[m].getName()), genericJson
                    );
                    if (!AdornerUtils.ignoreType((Class<?>) actualTypeArgument)) {
                        multiTable.append(paramTable((Class<?>)actualTypeArgument)).append(NEWLINE);
                    }
                }
            }
            paramJsons[i] = onlyOne ?
                    paramJson :
                    String.format(
                            "\n\t%s: %s",
                            parameters[paramMap.index].getName(),
                            (i == size - 1)?
                                    paramJson : paramJson + COMMA
                    );
            resultTable.append(multiTable.toString()).append(NEWLINE);
        }

        for (String json : paramJsons) {
            if (StringUtils.isEmpty(json)) {
                continue;
            }
            resultJson.append(json);
        }

        String json = onlyOne ? resultJson.toString() : String.format("{%s\n}", resultJson);
        return ParamPair.of(resultTable.toString(), json);
    }


    protected ParamPair responseJson(Method method) throws AdornerException {
        StringBuilder resultJson = new StringBuilder();
        Class<?> returnType = method.getReturnType();
        Type genericReturnType = method.getGenericReturnType();
        TypeVariable<? extends Class<?>>[] typeParameters = returnType.getTypeParameters();

        String table = typeParameters.length == 0 ? paramTable(returnType) : null;
        resultJson.append(fieldsJson(returnType, 1));

        if (ParameterizedType.class.isInstance(genericReturnType)) {
            ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

            if (typeParameters.length != actualTypeArguments.length) {
                return ParamPair.of(String.format(resultJson.toString(), "{}"));
            }

            StringBuilder multiTable = new StringBuilder();
            for (int i = 0, length = actualTypeArguments.length; i < length; i++) {
                Type actualTypeArgument = actualTypeArguments[i];
                String genericJson = genericTypeValue(actualTypeArgument);
                resultJson = new StringBuilder(
                        resultJson.toString().replaceAll(
                                genericPlaceholder(typeParameters[i].getName()), genericJson
                        )
                );
                if (!AdornerUtils.ignoreType((Class<?>) actualTypeArgument)) {
                    multiTable.append(paramTable((Class<?>)actualTypeArgument)).append(LINE_BREAK);
                }
            }
            return ParamPair.of(multiTable.toString(), resultJson.toString());
        } else {
            // 响应参数对象是泛型，但没设置实际泛型类型
            for (TypeVariable<? extends Class<?>> typeParameter : typeParameters) {
                resultJson = new StringBuilder(
                        resultJson.toString().replaceAll(
                                genericPlaceholder(typeParameter.getName()), "{}"
                        )
                );
            }
        }

        return ParamPair.of(table, resultJson.toString());
    }

    protected String fieldsJson(Class<?> clazz, int level) {
        String className = clazz.getName() + ClassUtils.SPOT;
        StringBuilder json = new StringBuilder();
        json.append(JSON_LEFT_SYMBOL).append(NEWLINE);
        List<Field> fields = new ArrayList<>();
        ClassUtils.fields(clazz, fields);
        String tab = printCount(TAB, level);
        String lastTab = printCount(TAB, level - 1);
        String doubleTab = printCount(TAB, 2);
        level = level + 1;

        for (int i = 0, length = fields.size(); i < length; i++) {
            Field field = fields.get(i);
            String key = field.getName();
            String fieldNotes;

            if (field.isAnnotationPresent(FieldNote.class)) {
                FieldNote fieldNote = field.getAnnotation(FieldNote.class);
                fieldNotes = (fieldNote.required() ? EMPTY : "可选, ") + fieldNote.value();
            } else {
                fieldNotes = DocContainer.CONTAINER.fieldGet(className + key, EMPTY);
            }

            String value = value(field, level);

            String note = EMPTY;
            if (notes() && StringUtils.isNotEmpty(fieldNotes)) {
                note = doubleTab + JS_SINGLE_LINE_COMMENTS + fieldNotes;
            }

            String end = (i == length - 1) ? note : COMMA + note + NEWLINE;
            json.append(tab).append("\"").append(key).append("\"").append(COLON).append(value).append(end);
        }
        json.append(NEWLINE).append(lastTab).append(JSON_RIGHT_SYMBOL);

        return json.toString();
    }

    protected String printCount(String text, int count) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < count; i++) {
            stringBuilder.append(text);
        }
        return stringBuilder.toString();
    }

    protected String genericTypeValue(Type actualTypeArgument) {
        return genericTypeValue(actualTypeArgument, 2);
    }

    protected String genericTypeValue(Type actualTypeArgument, int level) {
        Class<?> type = (Class<?>) actualTypeArgument;
        String genericJson;
        if (Number.class.isAssignableFrom(type)) {
            genericJson = "0";
        } else if (CharSequence.class.isAssignableFrom(type) ||
                Character.class.isAssignableFrom(type)
        ) {
            genericJson = "";
            genericJson = "\"" + genericJson + "\"";
        } else if (Date.class.isAssignableFrom(type)) {
            genericJson = LocalDate.now().format(AdornerConstants.DATE_FORMAT);
            genericJson = "\"" + genericJson + "\"";
        } else if (Boolean.class.isAssignableFrom(type)) {
            genericJson = "false";
        } else if (type.isArray() || List.class.isAssignableFrom(type)) {
            genericJson = "[]";
        } else if (Map.class.isAssignableFrom(type)) {
            genericJson = "{}";
        } else {
            genericJson = fieldsJson(type, level);
        }
        return genericJson;
    }

    /**
     * 泛型占位符
     *
     * @param genericType
     *      泛型类型字符
     * @return
     *      泛型占位符
     */
    protected String genericPlaceholder(String genericType) {
        return String.format("#-%s-#", genericType);
    }

    private String value(Field field, int level) {
        Class<?> type = field.getType();
        String value;
        // 基本数据类型
        if (type.isPrimitive()) {
            value = defaultValue.containsKey(type.getName()) ? defaultValue.get(type.getName()) : null;
        } else if (Number.class.isAssignableFrom(type)) {
            value = "0";
        } else if (CharSequence.class.isAssignableFrom(type) ||
                Character.class.isAssignableFrom(type)
        ) {
            value = "";
            value = "\"" + value + "\"";
        } else if (Date.class.isAssignableFrom(type)) {
            value = LocalDate.now().format(AdornerConstants.DATE_FORMAT);
            value = "\"" + value + "\"";
        } else if (Boolean.class.isAssignableFrom(type)) {
            value = "false";
        } else if (type.isArray() || Collection.class.isAssignableFrom(type)) {
            Class<?> componentType = type.getComponentType();
            value = "";

            // 数组类型
            if (componentType != null) {
                boolean ignore = AdornerUtils.ignoreType(componentType);

                if (!ignore) {
                    value = fieldsJson(componentType, level);
                }
            } else {
                Type genericType = field.getGenericType();

                if (ParameterizedType.class.isInstance(genericType)) {
                    ParameterizedType parameterizedType = (ParameterizedType) genericType;
                    Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
                    value = genericTypeValue(actualTypeArgument, level);
                }
            }
            value = String.format("[%s]", value);
        } else if (Map.class.isAssignableFrom(type)) {
            value = "{}";
        } else if (!field.getType().getName().equals(field.getGenericType().getTypeName())) {
            value = genericPlaceholder(field.getGenericType().getTypeName());
        } else {
            value = fieldsJson(type, level);
        }
        return value;
    }

    private static class ParamMap {
        private Class<?> clazz;
        private int index;

        public ParamMap(Class<?> clazz, int index) {
            this.clazz = clazz;
            this.index = index;
        }
    }
}